<?php
namespace app;
/**
 * 
 */
class Server
{
    private $sockets = [];//所有socket连接
    private $master;
    public function __construct($address, $port)
    {
        try {
            set_time_limit(0); 
            $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            // socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
            socket_bind($this->master, $address, $port);
            socket_listen($this->master);
            
            socket_set_nonblock($this->master);
        }catch (\Exception $e) {
            $err_code = socket_last_error();
            $err_msg = socket_strerror($err_code);
        }
        $this->sockets = [$this->master];//通讯节点
        while (true) {
            try {
                $this->handle();
            } catch (\Exception $e) {
                $this->log('error_do_server '.$e->getCode().'-'.$e->getMessage());
            }
        }
    }

    private function handle ()
    {
        $read = $this->sockets;
        $write = $except = null;
        //socket_select IO复用，在一个进程内可以维持最多 1024 个连接
        if (socket_select($read, $write, $except, 0, 0) !== false) {
            if (in_array($this->master, $read)) {
                if (($newc = socket_accept($this->master)) !== false) {
                    // $this->sockets[(int)$newc] = $newc;
                    $header = socket_read($newc, 1024);
                    $this->handshake($newc,$header);
                    $socket_key = array_search($this->master, $read);
                    unset($read[$socket_key]);
                }
            }

            foreach ($read as $key => $value) {
                $bytes = socket_recv($value, $buf, 2048, 0);
                if ($bytes === false) {
                    unset($read[$key]);
                }
                if ($bytes > 9) {
                    $arr = self::parse($buf);
                    $content = $this->build(json_encode($arr));
                    if (!empty($arr)) {
                        foreach ($this->sockets as $k => $v) {
                            if ($v !== $this->master) {
                                if ($k == $arr['user_id']) {
                                    continue;
                                }
                                socket_write($v, $content, strlen($content));
                            }
                        }
                    }
                }
            }
        }
    }
    //将新的连接加入$this->socket
    public function connect ($newc)
    {
        socket_getpeername($newc, $ip, $port);
        $socket_info = [
            'resource' => $newc,   //新的连接
            'uname' => '',
            'handshake' => true,  //是否已经握手
            'ip' => $ip,
            'port' => $port,
        ];

        $this->socket[(int)$newc] = $socket_info;
        $header = socket_read($newc, 1024);
        $this->handShake($newc,$header);
        // $msg = [
        //     'content' => '你好阿'
        // ];
        // $msg = $this->build(json_encode($msg));
        // socket_write($newc, $msg, strlen($msg));
        // $bytes = socket_recv($newc, $buf, 2048, MSG_WAITALL);
        // self::log($buf,1);

    }

    private function log ($text,$is_serialize = 2)
    {
        $path = '/var/www/html/socket/socket/app/log/debug.log';
        $time = date("Y-m-d H:i:s");
        if ($is_serialize == 1) $text = serialize($text);
        file_put_contents($path, $time.'-'.$text . "\r\n", FILE_APPEND);
    }
    /**
     * 广播消息
     *
     * @param $data
     */
    private function broadcast($data) {
        foreach ($this->sockets as $socket) {
            if ($socket['resource'] == $this->master) {
                continue;
            }
            socket_write($socket['resource'], $data, strlen($data));
        }
    }
    /**
     * 用公共握手算法握手
     *
     * @param $socket
     * @param $buffer
     *
     * @return bool
     */
    public function handShake($val, $buffer) {
        // 获取到客户端的升级密匙
        $line_with_key = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
        $key = trim(substr($line_with_key, 0, strpos($line_with_key, "\r\n")));

        // 生成升级密匙,并拼接websocket升级头
        $upgrade_key = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));// 升级key的算法
        $upgrade_message = "HTTP/1.1 101 Switching Protocols\r\n";
        $upgrade_message .= "Upgrade: websocket\r\n";
        $upgrade_message .= "Sec-WebSocket-Version: 13\r\n";
        $upgrade_message .= "Connection: Upgrade\r\n";
        $upgrade_message .= "Sec-WebSocket-Accept:" . $upgrade_key . "\r\n\r\n";

        socket_write($val, $upgrade_message, strlen($upgrade_message));// 向socket里写入升级信息
        //获取客户端传来的用户id,并绑定对应的套接字
        $bytes = socket_recv($val, $buf, 2048, 0);
        if ($bytes !== false && $bytes >= 9) {
            $user_id = (self::parse($buf))['user_id'];
            $this->sockets[(int)$user_id] = $val;
        }else {
            $msg = $this->build(json_encode(['code' => 2, 'msg' => '连接失败']));
            socket_write($val, $msg, strlen($msg));
        }
    }

    /**
     * 解析数据
     *
     * @param $buffer
     *
     * @return bool|string
     */
    private function parse($buffer) {
        $decoded = '';
        $len = ord($buffer[1]) & 127;
        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return json_decode($decoded, true);
    }

    /**
     * 将普通信息组装成websocket数据帧
     *
     * @param $msg
     *
     * @return string
     */
    private function build($msg) {
        $frame = [];
        $frame[0] = '81';
        $len = strlen($msg);
        if ($len < 126) {
            $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } else if ($len < 65025) {
            $s = dechex($len);
            $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
        } else {
            $s = dechex($len);
            $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
        }

        $data = '';
        $l = strlen($msg);
        for ($i = 0; $i < $l; $i++) {
            $data .= dechex(ord($msg{$i}));
        }
        $frame[2] = $data;

        $data = implode('', $frame);

        return pack("H*", $data);
    }

    /**
     * 客户端关闭连接
     *
     * @param $socket
     *
     * @return array
     */
    private function disconnect($socket) {
        $recv_msg = [
            'type' => 'logout',
            'content' => $this->sockets[(int)$socket]['uname'],
        ];
        unset($this->sockets[(int)$socket]);

        return $recv_msg;
    }

    /**
     * 拼装信息
     *
     * @param $socket
     * @param $recv_msg
     *          [
     *          'type'=>user/login
     *          'content'=>content
     *          ]
     *
     * @return string
     */
    private function dealMsg($val, $recv_msg) {
        $msg_type = $recv_msg['type'];
        $msg_content = $recv_msg['content'];
        $response = [];

        switch ($msg_type) {
            case 'login':
                $this->socket[(int)$val]['uname'] = $msg_content;
                // 取得最新的名字记录
                $user_list = array_column($this->socket, 'uname');
                $response['type'] = 'login';
                $response['content'] = $msg_content;
                $response['user_list'] = $user_list;
                break;
            case 'logout':
                $user_list = array_column($this->socket, 'uname');
                $response['type'] = 'logout';
                $response['content'] = $msg_content;
                $response['user_list'] = $user_list;
                break;
            case 'user':
                $uname = $this->socket[(int)$val]['uname'];
                $response['type'] = 'user';
                $response['from'] = $uname;
                $response['content'] = $msg_content;
                break;
        }

        return $this->build(json_encode($response));
    }
}

// $ws = new Server("127.0.0.1", "8081");
$ws = new Server("10.3.141.169", "8081");


